package com.autonavi.yunda.yunji.core.service.dbfactory;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.autonavi.yunda.yunji.common.exception.AmapException;
import com.autonavi.yunda.yunji.common.exception.AmapExceptionCode;
import com.autonavi.yunda.yunji.common.exception.AmapPreconditions;
import com.autonavi.yunda.yunji.common.utils.JsonUtils;
import com.autonavi.yunda.yunji.core.enums.DBType;
import com.autonavi.yunda.yunji.core.enums.DatasourceType;
import com.autonavi.yunda.yunji.core.vo.datasource.config.MysqlDataConfig;
import com.autonavi.yunda.yunji.core.vo.datasource.config.PgsqlDataConfig;
import com.autonavi.yunda.yunji.core.vo.datasource.config.TDDLDataConfig;
import com.taobao.tddl.client.jdbc.TDataSource;
import com.taobao.tddl.common.exception.TddlRuntimeException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;


@Slf4j
@Repository
public class DbFactoryService extends AbstractDataSourceFactory<JdbcTemplate> {
    private static final String VALIDATION_QUERY = "SELECT 1";
    private final Map<String, PlatformTransactionManager> transactionManagerMap = new ConcurrentHashMap<>();

    @Getter
    @Value("${spring.application.name}")
    private String appName;

    @Override
    public DBType getDbType() {
        return DBType.SQL;
    }

    /**
     * 对象创建后执行初始化操作
     */
    @Override
    @PostConstruct
    protected void initDataSourceCache() {
        if (lazyInit) {
            log.info("DbFactoryService {} initDataSourceCache lazy!!", getDbType());
            return;
        }

        this.dataSourceConfigService.findByDataSourceTypeList(getDbType().getDatasourceTypes())
                .forEach(result -> {
                    try {
                        String dataSourceName = result.getLeft();
                        DatasourceType datasourceType = result.getMiddle();
                        String dataSourceConfig = result.getRight();
                        log.info("createDataSource name:{},type:{}", dataSourceName, dataSourceConfig);
                        JdbcTemplate dataSource = createDataSource(datasourceType, dataSourceConfig);
                        dataSourceVOMap.put(dataSourceName, dataSource);
                        //事务管理器初始化
                        transactionManagerMap.put(dataSourceName, new DataSourceTransactionManager(dataSource.getDataSource()));
                    } catch (Exception e) {
                        log.info("数据源初始化失败 name:{},type = {}", result.getLeft(), result.getMiddle(), e);
                    }
                });
        log.info("initDataSourceCache end");
    }

    @Override
    protected void createDataSourceByName(String dataSourceName) {
        try {
            if (!StringUtils.hasText(dataSourceName)) {
                throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "创建的数据源名称为空！");
            }
            Optional<Pair<DatasourceType, String>> optionalDatasource
                    = this.dataSourceConfigService.findDataSourceConfigByName(dataSourceName);
            if (optionalDatasource.isEmpty()) {
                throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "数据源配置不存在");
            }
            DatasourceType datasourceType = optionalDatasource.get().getLeft();
            String config = optionalDatasource.get().getRight();
            JdbcTemplate dataSource = createDataSource(datasourceType, config);
            dataSourceVOMap.put(dataSourceName, dataSource);
            transactionManagerMap.put(dataSourceName, new DataSourceTransactionManager(dataSource.getDataSource()));
        } catch (Exception e) {
            log.info("getInstanceByDb:" + e.getMessage(), e);
            throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "数据源创建失败:" + e.getMessage(), e);
        }
    }

    @Override
    public JdbcTemplate createDataSource(DatasourceType dataSourceType, String dataSourceConfig) {
        JdbcTemplate dataSource = null;
        if (DatasourceType.MYSQL.equals(dataSourceType)) {
            dataSource = createMysqlDataSource(dataSourceConfig);
        } else if (DatasourceType.TDDL.equals(dataSourceType)) {
            dataSource = createTdDataSource(dataSourceConfig);
        } else if (DatasourceType.PGSQL.equals(dataSourceType)) {
            dataSource = createPgsqlDataSource(dataSourceConfig);
        }
        return dataSource;
    }

    @Override
    public void close(JdbcTemplate jdbcTemplate) {
        if (jdbcTemplate == null) {
            return;
        }
        DataSource dataSource = jdbcTemplate.getDataSource();
        if (dataSource instanceof DruidDataSource) {
            ((DruidDataSource) dataSource).close();
        } else if (dataSource instanceof TDataSource) {
            ((TDataSource) dataSource).destroyDataSource();
        } else {
            log.warn("close dataSource fail:{}", dataSource.getClass().getName());
        }
    }

    @Override
    public void removeDataSource(String dataSourceName) {
        JdbcTemplate jdbcTemplate = dataSourceVOMap.get(dataSourceName);
        dataSourceVOMap.remove(dataSourceName);
        transactionManagerMap.remove(dataSourceName);
        close(jdbcTemplate);
    }

    @Override
    public void testConnection(String dataSourceName) {
        try {
            JdbcTemplate instance = getInstance(dataSourceName);
            System.out.println(JsonUtils.toString(instance.queryForMap(VALIDATION_QUERY)));
        } catch (TddlRuntimeException e) {
            log.warn("TddlRuntimeException testConnection error", e);
        } catch (Exception e) {
            throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, e.getMessage(), e);
        } finally {
            removeDataSource(dataSourceName);
        }
    }


    public PlatformTransactionManager getTransactionManager(String dataSourceName) {
        if (!StringUtils.hasText(dataSourceName)) {
            throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "dataSourceName 不可为空！");
        }
        log.debug("getInstance:" + dataSourceName);
        if (!dataSourceVOMap.containsKey(dataSourceName)) {
            synchronized (this) {
                if (!dataSourceVOMap.containsKey(dataSourceName)) {
                    createDataSourceByName(dataSourceName);
                    PlatformTransactionManager dbObj = transactionManagerMap.get(dataSourceName);
                    if (dbObj != null) {
                        return dbObj;
                    } else {
                        log.error("dataSourceName " + dataSourceName + " return null!");
                        throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "conf dataSourceName is not match");
                    }
                }
            }
        }
        return transactionManagerMap.get(dataSourceName);
    }

    /**
     * 创建tddl数据源
     */
    public static JdbcTemplate createTdDataSource(String configStr) {
        TDDLDataConfig config = JsonUtils.from(configStr, TDDLDataConfig.class);
        String appname = config.appname;
        Boolean dynamic = config.dynamic;
        Boolean sharding = config.sharding;
        String validationQuery = config.validation_query;
        if (!StringUtils.hasText(appname)) {
            return null;
        }
        TDataSource tdDataSource = new TDataSource();
        tdDataSource.setAppName(appname);
        tdDataSource.setDynamicRule(dynamic == null || dynamic);
        tdDataSource.setSharding(sharding != null && sharding);
        //pouch化需要setUnitName指向center
        tdDataSource.setUnitName("center");
        tdDataSource.init();
        JdbcTemplate db = new JdbcTemplate(tdDataSource);
        log.info(appname + "_ok");
        return db;
    }

    /**
     * 创建普通数据源
     */
    private static JdbcTemplate createMysqlDataSource(String configStr) {
        MysqlDataConfig config = JsonUtils.from(configStr, MysqlDataConfig.class);
        String url = config.url;
        String username = config.user_name;
        String password = config.password;
        String driver = config.driver;
        AmapPreconditions.checkArgument(StringUtils.hasLength(url));
        AmapPreconditions.checkArgument(StringUtils.hasLength(username));
        AmapPreconditions.checkArgument(StringUtils.hasLength(password));
        Map<String, Object> properties = new HashMap<>(8);
        properties.put("url", url);
        properties.put("username", username);
        properties.put("password", password);
        if (StringUtils.hasText(driver)) {
            properties.put("driverClassName", driver);
        } else {
            properties.put("driverClassName", "com.mysql.jdbc.Driver");
        }
        properties.put("keepAlive", "true");

        properties.put("validationQuery", VALIDATION_QUERY);
        properties.put("maxWait", "10000");
        properties.put("testOnBorrow", "false");
        properties.put("testWhileIdle", "true");
        properties.put("timeBetweenEvictionRunsMillis", "30000");
        properties.put("minEvictableIdleTimeMillis", "18000000");
        // 失败后重连的次数
        properties.put("connectionErrorRetryAttempts", "6");
        properties.put("breakAfterAcquireFailure", "true");
        //连接池信息日志打印
//        properties.put("druid.timeBetweenLogStatsMillis", "30000");
        DataSource druidDataSource;
        try {
            druidDataSource = DruidDataSourceFactory.createDataSource(properties);

        } catch (Exception e) {
            throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "创建 druidDataSource 数据源失败", e);
        }
        return new JdbcTemplate(druidDataSource);
    }

    /**
     * 创建pgsql数据源
     */
    protected static JdbcTemplate createPgsqlDataSource(String configStr) {
        PgsqlDataConfig config = JsonUtils.from(configStr, PgsqlDataConfig.class);
        String url = config.url;
        String username = config.user_name;
        String password = config.password;
        String driver = config.driver;
        AmapPreconditions.checkArgument(StringUtils.hasLength(url));
        AmapPreconditions.checkArgument(StringUtils.hasLength(username));
        AmapPreconditions.checkArgument(StringUtils.hasLength(password));
        Map<String, Object> properties = new HashMap<>(8);
        properties.put("url", url);
        properties.put("username", username);
        properties.put("password", password);
        if (StringUtils.hasText(driver)) {
            properties.put("driverClassName", driver);
        } else {
            properties.put("driverClassName", "org.postgresql.Driver");
        }
        properties.put("keepAlive", "true");
        properties.put("validationQuery", VALIDATION_QUERY);
        properties.put("testOnBorrow", "false");
        properties.put("testWhileIdle", "true");
        properties.put("timeBetweenEvictionRunsMillis", "30000");
        properties.put("minEvictableIdleTimeMillis", "18000000");
        // 失败后重连的次数
        properties.put("connectionErrorRetryAttempts", "6");
        // 请求失败之后中断
        properties.put("maxWait", "5000");
        DataSource druidDataSource;
        try {
            druidDataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, "创建 druidDataSource 数据源失败", e);
        }
        return new JdbcTemplate(druidDataSource);

    }

    public String getInstanceName(String dataSourceName, DatasourceType dataSourceType, String dataSourceConfig) {
        String instanceName = null;
        if (DatasourceType.MYSQL.equals(dataSourceType)) {
            String url = JsonUtils.from(dataSourceConfig, MysqlDataConfig.class).url;
            instanceName = getInstanceNameByUrl(url);
        } else if (DatasourceType.TDDL.equals(dataSourceType)) {
            String appName = JsonUtils.from(dataSourceConfig, TDDLDataConfig.class).appname;
            instanceName = appName.substring(0, appName.lastIndexOf("_")).toLowerCase();
        } else if (DatasourceType.PGSQL.equals(dataSourceType)) {
            String url = JsonUtils.from(dataSourceConfig, PgsqlDataConfig.class).url;
            instanceName = getInstanceNameByUrl(url);
        }
        if (instanceName == null) {
            log.error("dataSourceName " + dataSourceName + " instance is null!");
        }
        return instanceName;
    }


    private String getInstanceNameByUrl(String url) {
        if (!url.contains("?")) {
            return url.trim().substring(url.lastIndexOf("/") + 1).toLowerCase();
        } else {
            return url.trim().substring(url.lastIndexOf("/") + 1, url.indexOf("?"));
        }
    }
}
